/*
 * The server main for EGS. Creates a game manager and an 
 * authentication manager. Listens for client connections 
 * and spawns new client threads.
 */

package egs.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.Security;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import egs.comms.Connection;
import egs.comms.ServerConnection;
import egs.comms.StubConnection;
import egs.packets.*;

public class EGSServer implements Runnable {

    public static final int EGS_PORT = 13337;
    public static final int EGS_TLS_PORT = 13338;

    private Thread serverThread;
    private ServerSocket serverSocket;
    private GameManager gameManager;
    private AuthenticationManager authManager;
    private List<ClientThread> clientThreads;
    private static List<Short> securePorts;
    
    private static String user_db_file;

    static{
    	securePorts = new LinkedList<Short>();
    	for(short i = EGS_TLS_PORT; i<EGS_TLS_PORT+1000; i++){
    		securePorts.add(i);
    	}
    }

    public EGSServer() {
    	serverThread = new Thread(this);
    	gameManager = new GameManager();
    	authManager = new AuthenticationManager(user_db_file);
    	clientThreads = new LinkedList<ClientThread>();
    	Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
    	try {
    	    serverSocket = new ServerSocket(EGS_PORT);
    	} catch (IOException ioe) {
    	    System.err.println("Error starting game server: could not listen on port " + EGS_PORT);
    	}
    }

    public void run() {
	    while (isConnected()) {
			try {
			    Socket clientSocket = serverSocket.accept();
			    Connection connection = new ServerConnection(clientSocket);
			    ClientState clientState = new ClientState(connection);
			    ClientThread clientThread = new ClientThread(authManager, gameManager, clientState);
			    clientThreads.add(clientThread);
			} catch (IOException e) { }
	    }
    }

    public boolean isConnected() {
    	return serverSocket.isBound();
    }

    public void start() {
    	if (isConnected()) {
    		serverThread.start();
    	}
    }
    
    public void stop() {
    	try {
			serverSocket.close();
		} catch (IOException e) {
			System.err.println("Error stopping game server: could not close server socket.");
		}
    }
    
    public void test() {
    	List<Packet> pkts1 = new ArrayList<Packet>();
    	try {
    		pkts1.add(new UseSecurePacket(UseSecurePacket.SecurityCode.none));
			pkts1.add(new AuthLoginPacket("testuser1", "testpass1", new short[0][0]));
			pkts1.add(new SelectGamePacket((short)0, Packet.GAME_NAME_CHECKERS, (short)1));
			pkts1.add(new OppChoicePacket(Packet.GAME_NAME_CHECKERS, (short)1, true, ""));
		} catch (PacketException e) { e.printStackTrace(); }
		StubConnection connection1 = new StubConnection(pkts1, "testuser1");
    	ClientState clientState1 = new ClientState(connection1);
    	ClientThread clientThread1 = new ClientThread(authManager, gameManager, clientState1);
    	clientThreads.add(clientThread1);
    	
    	try { Thread.sleep(1000); } catch (InterruptedException e) { }
    	
    	List<Packet> pkts2 = new ArrayList<Packet>();
    	try {
    		pkts2.add(new UseSecurePacket(UseSecurePacket.SecurityCode.none));
			pkts2.add(new AuthLoginPacket("testuser2", "testpass2", new short[0][0]));
			pkts2.add(new SelectGamePacket((short)0, Packet.GAME_NAME_CHECKERS, (short)1));
			pkts2.add(new OppChoicePacket(Packet.GAME_NAME_CHECKERS, (short)1, false, "testuser1"));
		} catch (PacketException e) { e.printStackTrace(); }
		StubConnection connection2 = new StubConnection(pkts2, "testuser2");
    	ClientState clientState2 = new ClientState(connection2);
    	ClientThread clientThread2 = new ClientThread(authManager, gameManager, clientState2);
    	clientThreads.add(clientThread2);
    	
    	try { Thread.sleep(1000); } catch (InterruptedException e) { }
    	System.out.println("\nACK'ing offer");
    	
    	Packet pkt = null;
		try {
			pkt = new OppAckPacket(Packet.GAME_NAME_CHECKERS, (short)1, "testuser2");
		} catch (PacketException e) { }
    	connection1.addPacket(pkt);
    }

    public static void main( String argv[] ) {

        if ( !parseArgs( argv ) ) {
    		printHelp();
    		return;
    	}

    	EGSServer server = new EGSServer();
		if (!server.isConnected())
		    System.exit(-1);
		server.start();
		//server.test();
    }
    
  
    public static synchronized short getSecurePort(){
    	if(securePorts.size() > 0){
    		return securePorts.remove(0);
    	}else{
    		return -1;
    	
    	}
    		
    }
    
    public static synchronized void returnPort(short port){
    	securePorts.add(port);
    }


	private static void printHelp() {
        System.out.println( "EGS Game Server Usage:" );
        System.out.println( "  Required Args:" );
        System.out.println( "    -u <user_file_db>" );
	}


	private static boolean parseArgs(String[] argv) {
        boolean user_db_found = false;

        for ( int i = 0; i < argv.length; i++ ) {

            if ( argv[i].equals( "-u" ) ) {
                if ( i+1 == argv.length ) {
                    System.err.println( "Ran out of arguments parsing -s" );
                    return false;
                }
                if ( user_db_found ) {
                    System.err.println( "Invalid multiple use of -s" );
                    return false;
                }

                user_db_file = argv[i+1];
                user_db_found = true;

                System.out.println( "Using user_db_file: " + argv[i+1] );

                i++;

            } else {
                System.err.println( "Unknown argument: " + argv[i] );
            }
        }
        
		return (user_db_found);
	}

}
